use super::*;

mod lookup;
mod state;

pub use lookup::*;
pub use state::*;

pub trait BinarySearchValue: FromData {
	fn is_termination(&self) -> bool;
	fn contains(&self, glyph_id: GlyphId) -> core::cmp::Ordering;
}

/// A binary searching table as defined at
/// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html
#[derive(Clone)]
pub struct BinarySearchTable<'a, T: BinarySearchValue> {
	values: LazyArray16<'a, T>,
	len: core::num::NonZeroU16, // values length excluding termination segment
}
impl<'a, T: BinarySearchValue + core::fmt::Debug> BinarySearchTable<'a, T> {
	#[inline(never)]
	pub fn parse(data: &'a [u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		let segment_size = s.read::<u16>()?;
		let number_of_segments = s.read::<u16>()?;
		s.advance(6); // search_range + entry_selector + range_shift

		if usize::from(segment_size) != T::SIZE {
			return None;
		}

		if number_of_segments == 0 {
			return None;
		}

		let values = s.read_array16::<T>(number_of_segments)?;

		// 'The number of termination values that need to be included is table-specific.
		// The value that indicates binary search termination is 0xFFFF.'
		let mut len = number_of_segments;
		if values.last()?.is_termination() {
			len = len.checked_sub(1)?;
		}

		Some(BinarySearchTable {
			len: core::num::NonZeroU16::new(len)?,
			values,
		})
	}

	pub fn get(&self, key: GlyphId) -> Option<T> {
		let mut min = 0;
		let mut max = (self.len.get() as isize) - 1;
		while min <= max {
			let mid = (min + max) / 2;
			let v = self.values.get(mid as u16)?;
			match v.contains(key) {
				core::cmp::Ordering::Less => max = mid - 1,
				core::cmp::Ordering::Greater => min = mid + 1,
				core::cmp::Ordering::Equal => return Some(v),
			}
		}

		None
	}
}
impl<'a, T: BinarySearchValue + core::fmt::Debug + Copy> core::fmt::Debug for BinarySearchTable<'a, T> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("BinarySearchTable")
			.field("values", &self.values)
			.field("len", &self.len)
			.finish()
	}
}
